﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace AbaqusToFlac3d
{
	public partial class MainForm : Form
	{
		public MainForm()
		{
			InitializeComponent();

			_backgroundWorker = new BackgroundWorker()
			{
				WorkerReportsProgress = true,
			};
			_backgroundWorker.DoWork += _backgroundWorker_DoWork;
			_backgroundWorker.ProgressChanged += _backgroundWorker_ProgressChanged;
			_backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
		}

		private readonly BackgroundWorker _backgroundWorker;
		private string _inpFilePath;
		private string _flac3DFilePath;

		private void buttonConvert_Click(object sender, EventArgs e)
		{
			Debug.Assert(!_backgroundWorker.IsBusy);
			if ((string.IsNullOrEmpty(_inpFilePath) || !File.Exists(_inpFilePath))
				|| (string.IsNullOrEmpty(_flac3DFilePath)))
			{
				MessageBox.Show("Speficy the inp and flac3d file path first!");
				return;
			}

			listBoxProgress.Items.Clear();
			buttonConvert.Enabled = false;
			var param = new BackgroundWorkParameter()
			{
				InpFilePath = _inpFilePath,
				Flac3dFilePath = _flac3DFilePath,
			};
			_backgroundWorker.RunWorkerAsync(param);
		}

		private void textBoxInpFilePath_Click(object sender, EventArgs e)
		{
			var ofd = new OpenFileDialog()
			{
				DefaultExt = "inp",
				Multiselect = false,
				Filter = "ABAQUS Inp文件|*.inp"
			};
			if (ofd.ShowDialog() == DialogResult.OK)
			{
				_inpFilePath = ofd.FileName;
				textBoxInpFilePath.Text = _inpFilePath;

				if (string.IsNullOrEmpty(_flac3DFilePath))
				{
					_flac3DFilePath = _inpFilePath + ".f3grid";
					textBoxFlac3dFilePath.Text = _flac3DFilePath;
				}
			}
		}

		private void textBoxFlac3dFilePath_Click(object sender, EventArgs e)
		{
			var sfd = new SaveFileDialog()
			{
				DefaultExt = "f3grid",
			};
			if (sfd.ShowDialog() == DialogResult.OK)
			{
				_flac3DFilePath = sfd.FileName;
				textBoxFlac3dFilePath.Text = _flac3DFilePath;
			}
		}

		private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			buttonConvert.Enabled = true;
		}

		private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
		{
			var progressMsg = (string) e.UserState;
			listBoxProgress.Items.Add(progressMsg);
		}

		private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
		{
			var param = (BackgroundWorkParameter) e.Argument;
			var inpFilePath = param.InpFilePath;

			_backgroundWorker.ReportProgress(0, "Starting ......");

			//
			// Read keyword entries data
			//
			var keywords = new[] { "*NODE", "*ELEMENT", "*ELSET" };
			var keywordEntries = new List<KeywordEntry>();
			using (var sr = File.OpenText(inpFilePath))
			{
				_backgroundWorker.ReportProgress(0, "Reading inp file ......");

				Debug.Assert(!sr.EndOfStream);
				const StringComparison compareMode = StringComparison.OrdinalIgnoreCase;

				//
				// Read valid keyword line
				//
				var line = sr.ReadLine().Trim();
				while (!sr.EndOfStream)
				{
					while (keywords.All(keyword => !line.StartsWith(keyword, compareMode)) && !sr.EndOfStream)
						line = sr.ReadLine().Trim();
					if (sr.EndOfStream)
						break;

					var keywordLine = line;

					//
					// Read data lines
					//
					Debug.Assert(keywords.Any(keyword => line.StartsWith(keyword, compareMode)));
					var dataLines = new List<string>();
					line = sr.ReadLine().Trim();
					while (!line.StartsWith("*"))
					{
						dataLines.Add(line);
						if (sr.EndOfStream)
							break;

						line = sr.ReadLine().Trim();
					}

					keywordEntries.Add(new KeywordEntry(keywordLine, dataLines));
				}
			}

			//
			// Get node data
			//
			_backgroundWorker.ReportProgress(0, "Getting node data ......");

			var nodeData = new List<Node>();
			var entry = keywordEntries.FirstOrDefault(ke => ke.Keyword == "*NODE");
			if (entry == null)
				return;
			nodeData.AddRange(entry.DataLines.Select(ParseNodeDataLine));

			//
			// Get element data
			//
			_backgroundWorker.ReportProgress(0, "Getting element data ......");

			Func<KeywordEntry, string, bool> hasElemTypeOf =
				(ke, elemTypeName) => ke.Keyword == "*ELEMENT" && ke.Options.ContainsKey("TYPE") && ke.Options["TYPE"] == elemTypeName;

			var C3D8ElemData = keywordEntries
				.Where(ke => hasElemTypeOf(ke, "C3D8"))
				.SelectMany(ke => ke.DataLines.Select(ParseElemDataLine)).ToList();

			var C3D4ElemData = keywordEntries
				.Where(ke => hasElemTypeOf(ke, "C3D4"))
				.SelectMany(ke => ke.DataLines.Select(ParseElemDataLine)).ToList();

			var C3D5ElemData = keywordEntries
				.Where(ke => hasElemTypeOf(ke, "C3D5"))
				.SelectMany(ke => ke.DataLines.Select(ParseElemDataLine)).ToList();

			var C3D6ElemData = keywordEntries
				.Where(ke => hasElemTypeOf(ke, "C3D6"))
				.SelectMany(ke => ke.DataLines.Select(ParseElemDataLine)).ToList();

			//
			// Get element set data
			//
			_backgroundWorker.ReportProgress(0, "Getting element set data ......");

			Func<KeywordEntry, bool> isElsetEntry =
				ke => ke.Keyword == "*ELSET" && ke.Options.ContainsKey("ELSET") && !string.IsNullOrEmpty(ke.Options["ELSET"]);
			var elemSetData = keywordEntries.Where(ke => isElsetEntry(ke))
				.Select(ParseElemSetDataEntry).ToList();

			//
			// Output mesh data
			//
			var flac3dFilePath = param.Flac3dFilePath;
			using (var fs = File.CreateText(flac3dFilePath))
			{
				//
				// Output node data
				//
				_backgroundWorker.ReportProgress(0, "Output node data ......");

				Debug.Assert(nodeData.Count > 0);
				fs.WriteLine("*GRIDPOINTS");
				foreach (var node in nodeData)
				{
					var id = node.Id;
					var x = node.X;
					var y = node.Y;
					var z = node.Z;
					fs.WriteLine($"G {id},{x:E3},{y:E3},{z:E3}");
				}

				//
				// Output elem data
				//
				_backgroundWorker.ReportProgress(0, "Output element data ......");

				fs.WriteLine("*ZONES");
				foreach (var elem in C3D8ElemData)
				{
					Debug.Assert(elem.NodeIds.Count == 8);
					var id = elem.Id;
					var p0 = elem.NodeIds[0];
					var p1 = elem.NodeIds[1];
					var p2 = elem.NodeIds[3];
					var p3 = elem.NodeIds[4];
					var p4 = elem.NodeIds[2];
					var p5 = elem.NodeIds[7];
					var p6 = elem.NodeIds[5];
					var p7 = elem.NodeIds[6];
					fs.WriteLine($"Z B8 {id},{p0},{p1},{p2},{p3},{p4},{p5},{p6},{p7}");
				}

				foreach (var elem in C3D4ElemData)
				{
					Debug.Assert(elem.NodeIds.Count == 4);
					var id = elem.Id;
					var p0 = elem.NodeIds[0];
					var p1 = elem.NodeIds[1];
					var p2 = elem.NodeIds[2];
					var p3 = elem.NodeIds[3];
					fs.WriteLine($"Z T4 {id},{p0},{p1},{p2},{p3}");
				}

				foreach (var elem in C3D5ElemData)
				{
					Debug.Assert(elem.NodeIds.Count == 5);
					var id = elem.Id;
					var p0 = elem.NodeIds[0];
					var p1 = elem.NodeIds[1];
					var p2 = elem.NodeIds[3];
					var p3 = elem.NodeIds[4];
					var p4 = elem.NodeIds[2];
					fs.WriteLine($"Z P5 {id},{p0},{p1},{p2},{p3},{p4}");
				}

				foreach (var elem in C3D6ElemData)
				{
					Debug.Assert(elem.NodeIds.Count == 6);
					var id = elem.Id;
					var p0 = elem.NodeIds[0];
					var p1 = elem.NodeIds[2];
					var p2 = elem.NodeIds[3];
					var p3 = elem.NodeIds[1];
					var p4 = elem.NodeIds[5];
					var p5 = elem.NodeIds[4];
					fs.WriteLine($"Z W6 {id},{p0},{p1},{p2},{p3},{p4},{p5}");
				}

				//
				// Output elem set data
				//
				_backgroundWorker.ReportProgress(0, "Output element set data ......");

				fs.WriteLine("*GROUPS");
				foreach (var elemSet in elemSetData)
				{
					fs.WriteLine($"ZGROUP \'{elemSet.Name}\'");
					const int idCountPerLine = 15;
					var idCount = 0;
					foreach (var elemId in elemSet.ElemIds)
					{
						fs.Write($"{elemId} ");
						idCount++;
						if (idCount >= idCountPerLine)
						{
							fs.Write(Environment.NewLine);
							idCount = 0;
						}
					}
					if (idCount < idCountPerLine)
						fs.Write(Environment.NewLine);
				}
			}

			_backgroundWorker.ReportProgress(100, "Done!");
		}

		public static Node ParseNodeDataLine(string dataLine)
		{
			var tokens = dataLine.Trim().Split(',');
			Debug.Assert(tokens.Length == 4);
			var id = int.Parse(tokens[0].Trim());
			var x = double.Parse(tokens[1].Trim());
			var y = double.Parse(tokens[2].Trim());
			var z = double.Parse(tokens[3].Trim());
			return new Node(id, x, y, z);
		}

		public static Elem ParseElemDataLine(string dataLine)
		{
			var tokens = dataLine.Trim().Split(',').ToList();
			Debug.Assert(tokens.Count >= 5);
			var id = int.Parse(tokens[0].Trim());
			var nodeIds = tokens.GetRange(1, tokens.Count - 1).Select(token => int.Parse(token.Trim())).ToList();
			return new Elem(id, nodeIds);
		}

		public static ElemSet ParseElemSetDataEntry(KeywordEntry ke)
		{
			Debug.Assert(ke.Keyword == "*ELSET");
			var name = ke.Options["ELSET"];
			var elemIds = ke.DataLines.SelectMany(line => line.Split(',')
				.Where(t => !string.IsNullOrEmpty(t))
				.Select(t => int.Parse(t))).ToList();
			return new ElemSet(name, elemIds);
		}

		private class BackgroundWorkParameter
		{
			public string InpFilePath { get; set; }
			public string Flac3dFilePath { get; set; }
		}
	}

	public class Node
	{
		public Node(int id, double x, double y, double z)
		{
			Id = id;
			X = x;
			Y = y;
			Z = z;
		}

		public int Id { get; }
		public double X { get; }
		public double Y { get; }
		public double Z { get; }
	}

	public class Elem
	{
		public Elem(int id, List<int> nodeIds)
		{
			this.Id = id;
			this.NodeIds = nodeIds;
		}

		public int Id { get; }
		public List<int> NodeIds { get; }
	}

	public class ElemSet
	{
		public ElemSet(string name, List<int> elemIds)
		{
			this.Name = name;
			this.ElemIds = elemIds;
		}

		public string Name { get; }
		public List<int> ElemIds { get; } 
	}

	public class KeywordEntry
	{
		public KeywordEntry(string keywordLine, List<string> dataLines)
		{
			Debug.Assert(keywordLine.StartsWith("*") && !keywordLine.StartsWith("**"));
			var tokens = keywordLine.Split(',').ToList();
			Debug.Assert(tokens.Count >= 1);
			this.Keyword = tokens[0].Trim().ToUpper();

			if (tokens.Count > 1)
			{
				for (var i = 1; i < tokens.Count; i++)
				{
					var kvs = tokens[i].Split('=');
					Debug.Assert(1 <= kvs.Length && kvs.Length <= 2);
					this.Options.Add(kvs[0].Trim().ToUpper(), kvs.Length > 1 ? kvs[1].Trim() : "");
				}
			}

			this.DataLines = dataLines;
		}

		public string Keyword { get; }
		public Dictionary<string,string> Options { get; } = new Dictionary<string, string>();
		public List<string> DataLines { get; }
	}

	public class Option
	{
		public Option(string key, string value="")
		{
			this.Key = key;
			this.Value = value;
		}

		public string Key { get; }
		public string Value { get; }
	}
}
